#include "Drv_Gd5f4GQx.h"
#include <string.h>

#define PAGE_SIZE		4096
#define SPARE_SIZE		256			// 此数据的位置在页数据的后面

#define WRITE_COMMAND(cmd)	do {uint8_t ui8Cmd = cmd; m_pHndSpi->SendRecv(&ui8Cmd,1);}while (0);
#define WRITE_ENABLE()		WRITE_COMMAND(0x06);
#define WRITE_DISABLE()		WRITE_COMMAND(0x04);
#define GET_STATUS(addr,status)	do {uint8_t ui8aTmp[3] = {0x0F,addr,0x00};m_pHndSpi->SendRecv(ui8aTmp,3);status = ui8aTmp[2];}while (0);

Drv_Gd5f4GQx::Drv_Gd5f4GQx(ISpi *pHndSpi)
{
    m_pHndSpi = pHndSpi;
	m_pHndGpioChipSel = new GPIOHandler(CHIP_SEL_PORT,CHIP_SEL_PIN,e_GPIOMode_PPU);
	m_pHndGpioChipSel->Init();
}

Drv_Gd5f4GQx::~Drv_Gd5f4GQx(void)
{
}

uint32_t Drv_Gd5f4GQx::GetPageSize(void)
{
	return PAGE_SIZE;
}

uint32_t Drv_Gd5f4GQx::GetBlockSize(void)
{
	return (PAGE_SIZE * 64);
}

//uint8_t ui8aBuffer[PAGE_SIZE + SPARE_SIZE];
bool Drv_Gd5f4GQx::Init(void)
{
	uint8_t ui8aCmdBuff[4] = {0x9F,0x00,0x00,0x00};
	
	// 发送复位命令
	m_pHndGpioChipSel->Reset();
	WRITE_COMMAND(0xFF);
	m_pHndGpioChipSel->Set();
		
	// 读取ID
	m_pHndGpioChipSel->Reset();
	m_pHndSpi->SendRecv(ui8aCmdBuff,4);
	m_pHndGpioChipSel->Set();
	
	ui8aCmdBuff[0] = 0x9F;
	ui8aCmdBuff[1] = 0x01;
	ui8aCmdBuff[2] = 0x00;
	ui8aCmdBuff[3] = 0x00;
	m_pHndGpioChipSel->Reset();
	m_pHndSpi->SendRecv(ui8aCmdBuff,4);
	m_pHndGpioChipSel->Set();
	
	m_ui16PageSize = 4 * 1024;
	m_ui32BlockSize = m_ui16PageSize * 64;
	
	// 解除保护
	ui8aCmdBuff[0] = 0x1F;
	ui8aCmdBuff[1] = 0xA0;
	ui8aCmdBuff[2] = 0x00;
	ui8aCmdBuff[3] = 0x00;
	m_pHndGpioChipSel->Reset();
	m_pHndSpi->SendRecv(ui8aCmdBuff,4);
	m_pHndGpioChipSel->Set();
	
	m_pHndGpioChipSel->Reset();
	
	// 上电时ECCSx,ECCEx标识的是Block0,Page0的状态
	uint8_t ui8Status;
	GET_STATUS(0xC0,ui8Status);
	
	uint8_t ui8ECC = (ui8Status >> 4) & 0x03;
	if (ui8ECC)
	{
		GET_STATUS(0xF0,ui8Status);
		ui8ECC = (ui8ECC << 2) | ((ui8Status >> 4) & 0x03);
		if (ui8ECC & 0x08)		// 高位有错说明错误BIT超过8，不能纠正
		{
			;		// 出错处理
		}
	}
	
	m_pHndGpioChipSel->Set();
	
	// 设置特征
	uint8_t ui8Feature = 0 			|
					//	0x01 << 7	|	// OTP_PRT
					//	0x01 << 6	|	// OTP_EN
						0x01 << 4	|	// ECC_EN
					//	0x01 << 0	|	// QE,quad IO operations can be executed
						0x00;
	ui8aCmdBuff[0] = 0x1F;
	ui8aCmdBuff[1] = 0xB0;
	ui8aCmdBuff[2] = ui8Feature;
	ui8aCmdBuff[3] = 0x00;
	m_pHndGpioChipSel->Reset();
	m_pHndSpi->SendRecv(ui8aCmdBuff,4);
	m_pHndGpioChipSel->Set();
	
	// 开头4页为OTP区域
#if 0
	this->Erase(3);

	this->Read(3,0,0,ui8aBuffer,PAGE_SIZE + SPARE_SIZE);
	bool bFlag = false;
	for (int i = 0;i < PAGE_SIZE;i ++) 
	{
		if (i % 256 == 0) bFlag = !bFlag;
		if (bFlag)
		{
			ui8aBuffer[i] = 255 - i % 256;
		}
		else
		{
			ui8aBuffer[i] = i % 256;
		}
	}
	for (int i = PAGE_SIZE;i < PAGE_SIZE + SPARE_SIZE;i ++) ui8aBuffer[i] = 0xFF;
	this->Write(3,0,376,ui8aBuffer,PAGE_SIZE - 376);
	this->Read(3,0,376,ui8aBuffer,PAGE_SIZE - 376);
	bFlag = false;
	for (int i = 376;i < PAGE_SIZE - 376;i ++) 
	{
		if (i % 256 == 0) bFlag = !bFlag;
		if (bFlag)
		{
			if (ui8aBuffer[i] != 255 - i % 256)
			{
				bFlag = bFlag;
			}
		}
		else
		{
			if (ui8aBuffer[i] != i % 256)
			{
				bFlag = bFlag;
			}
		}
	}
	this->Read(3,0,0,ui8aBuffer,25);
	
	ui8aBuffer[0] = 0x01;
	ui8aBuffer[1] = 0x02;
	ui8aBuffer[2] = 0x03;
	ui8aBuffer[3] = 0x04;
	ui8aBuffer[4] = 0x05;
	this->Write(3,0,PAGE_SIZE,ui8aBuffer,5);
	this->Read(3,0,PAGE_SIZE,ui8aBuffer,5);
	
	this->MarkBadBlock(3);
	if (this->IsBadBlock(3))
	{
		this->Read(3,0,0,ui8aBuffer,25);
	}
#endif

	return true;
}

bool Drv_Gd5f4GQx::Erase(uint16_t block)
{
	m_pHndGpioChipSel->Reset();
	WRITE_COMMAND(0x06);
	m_pHndGpioChipSel->Set();
	
	uint32_t ui32Addr = block << 6;
	
	m_pHndGpioChipSel->Reset();
	uint8_t ui8aCmd[4] = {0xD8,(ui32Addr >> 16) & 0xFF,(ui32Addr >> 8) & 0xFF,ui32Addr & 0xFF};
	m_pHndSpi->SendRecv(ui8aCmd,4);
	m_pHndGpioChipSel->Set();
	
	m_pHndGpioChipSel->Reset();
	volatile uint8_t ui8Status;
	while (1)
	{
		GET_STATUS(0xC0,ui8Status);
		if (0x00 == (ui8Status & 0x01)) break;		// 判断操作是否完成
	}
	m_pHndGpioChipSel->Set();
	
	return (ui8Status & 0x04) ? false : true;
}

bool Drv_Gd5f4GQx::IsBlockECC(uint16_t block)
{
	uint8_t ui8Status;
	GET_STATUS(0xC0,ui8Status);
	
	uint8_t ui8ECC = (ui8Status >> 4) & 0x03;
	if (! ui8ECC) return false;
	
	GET_STATUS(0xF0,ui8Status);
	ui8ECC = (ui8ECC << 2) | ((ui8Status >> 4) & 0x03);
	if (ui8ECC & 0x08)		// 高位有错说明错误BIT超过8，不能纠正
	{
		return true;		// 出错
	}
	
	return false;
}

uint32_t Drv_Gd5f4GQx::Read(uint16_t block,uint16_t page,uint16_t col,uint8_t *pui8Buff,uint16_t len)
{
	if (col + len > PAGE_SIZE + SPARE_SIZE) return 0;	// 读取不能大于页 + 256
	
	uint32_t ui32Addr = (block << 6) + page;
	uint8_t ui8aCmd[4];
	
	m_pHndGpioChipSel->Reset();	
	ui8aCmd[0] = 0x13;		// 发送命令读取到缓存
	ui8aCmd[1] = (ui32Addr >> 16) & 0xFF;
	ui8aCmd[2] = (ui32Addr >> 8) & 0xFF;
	ui8aCmd[3] = ui32Addr & 0xFF;
	m_pHndSpi->SendRecv(ui8aCmd,4);
	m_pHndGpioChipSel->Set();
	
	volatile uint16_t ui16Dly = 0x7FF;
	while (-- ui16Dly);
	
	m_pHndGpioChipSel->Reset();
	while (1)
	{
		volatile uint8_t ui8Status;
		GET_STATUS(0xC0,ui8Status);
		if (0x00 == (ui8Status & 0x01)) break;		// 判断操作是否完成
	}
	m_pHndGpioChipSel->Set();
	
	ui16Dly = 0x7FF;
	while (-- ui16Dly);
	
	m_pHndGpioChipSel->Reset();
	ui8aCmd[0] = 0x03;				// 发送命令从缓存读
	ui8aCmd[1] = (0 >> 8) & 0x1F;	// 这里用col做偏移，调试不对
	ui8aCmd[2] = 0 & 0xFF;
	ui8aCmd[3] = 0x00;
	m_pHndSpi->SendRecv(ui8aCmd,4);
	
	// 通过读取来获得偏移
	for (int i = 0;i < col;i ++) m_pHndSpi->SendRecv(ui8aCmd,1);
	
	m_pHndSpi->SendRecv(pui8Buff,len);
	m_pHndGpioChipSel->Set();
	
	return len;
}

uint32_t Drv_Gd5f4GQx::Read(uint16_t block,uint16_t page,uint8_t *pui8Buff,uint16_t len,uint8_t *oob,uint16_t oobLen)
{
	if (len > PAGE_SIZE || oobLen > SPARE_SIZE - 4) return 0;
	
	;	// 清除ECC状态
	
	this->Read(block,page,PAGE_SIZE + 4,oob,oobLen);		// 读取OOB
	this->Read(block,page,0,pui8Buff,len);
	
	if (IsBlockECC(block)) return 0;

	return len;
}

// 最大只能写4352=4096+256，继续写则丢弃
uint32_t Drv_Gd5f4GQx::Write(uint16_t block,uint16_t page,uint16_t col,uint8_t *pui8Buff,uint16_t len)
{
	if (col + len > PAGE_SIZE + SPARE_SIZE) return 0;	// 读取不能大于页 + 256
	
	uint32_t ui32Addr = (block << 6) + page;
	uint8_t ui8aCmd[4];

	m_pHndGpioChipSel->Reset();
	WRITE_COMMAND(0x06);			// 写使能命令
	m_pHndGpioChipSel->Set();
	
	m_pHndGpioChipSel->Reset();
	ui8aCmd[0] = 0x02;				// 发送字节地址0~4351
	ui8aCmd[1] = (col >> 8) & 0x1F;
	ui8aCmd[2] = col & 0xFF;
	m_pHndSpi->SendRecv(ui8aCmd,3);
	m_pHndSpi->SendRecv(pui8Buff,len);
	m_pHndGpioChipSel->Set();

	m_pHndGpioChipSel->Reset();
	ui8aCmd[0] = 0x10;				// 启动编程
	ui8aCmd[1] = (ui32Addr >> 16) & 0xFF;
	ui8aCmd[2] = (ui32Addr >> 8) & 0xFF;
	ui8aCmd[3] = ui32Addr & 0xFF;
	m_pHndSpi->SendRecv(ui8aCmd,4);
	m_pHndGpioChipSel->Set();	// 发完命令之后读取状态必须要让CS变化(变高)
	
	volatile uint16_t ui16Dly = 0x7FF;
	while (-- ui16Dly);
	
	m_pHndGpioChipSel->Reset();
	volatile uint8_t ui8Status;
	while (1)
	{
		GET_STATUS(0xC0,ui8Status);
		if (0x00 == (ui8Status & 0x01)) break;		// 判断操作是否完成
	}
	m_pHndGpioChipSel->Set();
	if (ui8Status & 0x08) return 0;					// 编程失败

	m_pHndGpioChipSel->Reset();
	WRITE_DISABLE();
	m_pHndGpioChipSel->Set();
	
	return len;
}

uint32_t Drv_Gd5f4GQx::Write(uint16_t block,uint16_t page,uint8_t *pui8Buff,uint16_t len,uint8_t *oob,uint16_t oobLen)
{
	if (len > PAGE_SIZE || oobLen > SPARE_SIZE - 4) return 0;
	
	this->Write(block,page,PAGE_SIZE + 4,oob,oobLen);
	this->Write(block,page,0,pui8Buff,len);
	
	return len;
}

bool Drv_Gd5f4GQx::IsBadBlock(uint16_t block)
{
	uint8_t ui8aOOB[4];		// User meta 0 data I
	
	this->Read(block,0,PAGE_SIZE,ui8aOOB,4);	// 每个块的第一页的SPARE开头俩自己为0xFF表示好块
	if (0xFF == ui8aOOB[0] && 0xFF == ui8aOOB[1] && 0xFF == ui8aOOB[2] && 0xFF == ui8aOOB[3]) return false;
	return true;
}

void Drv_Gd5f4GQx::MarkBadBlock(uint16_t block)
{
	uint8_t ui8aOOB[4] = {0x00,0x00,0x00,0x00};
	this->Write(block,0,PAGE_SIZE,ui8aOOB,4);
}
